<script setup lang="ts">
import SparkMD5 from "spark-md5";
import { ref } from "vue";

//1MB=1024KB = 1024 * 1024B
const CHUNK_SIZE = 1024 * 1024; //1MB

const fileName = ref<string>("");
const fileHash = ref<string>("");
// 文件分片
const createdChunks = (file: File) => {
  let cur = 0;
  let chunks = [];
  while (cur < file.size) {
    const blob = file.slice(cur, cur + CHUNK_SIZE);

    chunks.push(blob);

    cur += CHUNK_SIZE;
  }
  return chunks;
};

//hash计算
const calculateHash = (chunks: Blob[]) => {
  return new Promise((resolve, reject) => {
    //  1.第一个和最后一个切片全部参与计算
    //  2.中间切片只计算 前面两个字节 中间两个字节 后面两个字节
    const targets: Blob[] = [];

    chunks.forEach((chunk, index) => {
      // 第一个和最后一个全部参与计算
      if (index == 0 || index == chunks.length - 1) {
        targets.push(chunk);
      } else {
        //  2.中间切片只计算 前面两个字节 中间两个字节 后面两个字节
        targets.push(chunk.slice(0, 2)); //前面两个字节
        targets.push(chunk.slice(CHUNK_SIZE / 2, CHUNK_SIZE / 2 + 2)); //中间两个字节
        targets.push(chunk.slice(CHUNK_SIZE - 2, CHUNK_SIZE)); //后面两个字节
      }
    });

    // 初始化一个SparkMD5对象，用于后续计算文件的MD5值。
    const spark = new SparkMD5();

    // 初始化一个FileReader对象，用于读取文件。
    const fileReader = new FileReader();

    //  使用Blob对象构造函数创建一个包含targets数组中文件的Blob对象，然后通过FileReader的readAsArrayBuffer方法读取该Blob对象。
    fileReader.readAsArrayBuffer(new Blob(targets));

    // 当文件读取完成后，通过SparkMD5对象计算文件内容的MD5值，
    fileReader.onload = (e) => {
      const result = e.target?.result;
      if (result) {
        spark.append(result);
        resolve(spark.end());
      }
      reject("计算失败");
    };
  });
};

// 上传分片
const uploadChunks = async (chunks: Blob[], uploadedList: string[]) => {
  // 将分块数据转换为包含文件哈希和块哈希的数据对象数组
  const data = chunks.map((chunk, index) => ({
    fileHash: fileHash.value,
    chunkHash: `${fileHash.value}-${index}`,
    chunk,
  }));

  // 根据数据对象数组生成FormData对象数组，用于提交给服务器
  const formDatas = data
    //实现断点续传过滤出服务器没有的分片
    .filter((item) => !uploadedList.includes(item.chunkHash))
    .map((item) => {
      const formData = new FormData();
      formData.append("fileHash", item.fileHash);
      formData.append("chunkHash", item.chunkHash);
      formData.append("chunk", item.chunk);
      return formData;
    });

  // 定义最大并发任务数
  const max = 6;

  // 用于存储并发上传任务的数组
  let tasks = [];

  for (let i = 0; i < formDatas.length; i++) {
    console.log(i, "========");

    // 等待队列长度达到最大并发数时，等待所有任务完成
    if (tasks.length >= max) {
      await Promise.all(tasks);
      tasks = []; // 清空任务队列
    }

    // 创建fetch任务，提交FormData对象到服务器
    const task = fetch("http://localhost:3000/upload", {
      method: "POST",
      body: formDatas[i],
    })
      .then(() => console.log(`Chunk ${i} uploaded successfully`))
      .catch((error) => console.error(`Chunk ${i} failed to upload: ${error}`));

    tasks.push(task);
  }

  // 等待剩余的任务完成
  if (tasks.length > 0) {
    await Promise.all(tasks);
    merageRequest();
  }
};
// 合并分片
const merageRequest = () => {
  fetch("http://localhost:3000/merge", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      fileHash: fileHash.value,
      fileName: fileName.value,
      size: CHUNK_SIZE,
    }),
  })
    .then(() => alert("合并成功"))
    .catch(() => alert("合并失败"));
};

const verify = () => {
  return fetch("http://localhost:3000/verify", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      fileHash: fileHash.value,
      fileName: fileName.value,
    }),
  })
    .then((res) => res.json())
    .then((res) => res);
};
const handleUpdate = async (e: Event) => {
  const files = (e.target as HTMLInputElement).files; //伪数组
  if (!files) return;

  // 读取文件
  console.log(files[0]);

  // 保存文件名
  fileName.value = files[0].name;

  // 文件分片
  const chunks = createdChunks(files[0]);
  console.log(chunks);

  //hash计算
  const hash = await calculateHash(chunks);
  console.log("hash: " + hash);

  //保存hash
  fileHash.value = hash as string;

  //校验hash值服务器是否存在
  const data = await verify();
  console.log(data);

  if (!data.data.shouldUpload) {
    alert("秒传");
    return;
  }
  // 上传分片
  uploadChunks(chunks, data.data.uploadedList);
};
</script>
<template>
  <h1>大文件上传</h1>
  <input @change="handleUpdate" type="file" />
</template>

<style scoped></style>
